#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include <libsoup/soup.h>

#include "db.h"
#include "util.h"
#include "json.h"

static sqlite3 *sqlite_open(const char *dbfile) {
	sqlite3 *db = NULL;
	int retval = 0;

	retval = sqlite3_open(dbfile, &db);
	if (retval != SQLITE_OK) {
		printf("%s\n", sqlite3_errmsg(db));
		sqlite3_close(db);
	}

	return db;
}

static int sqlite_begin_transaction(sqlite3 *db) {
	return sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
}

static int sqlite_commit_transaction(sqlite3 *db) {
	return sqlite3_exec(db, "COMMIT TRANSACTION;", NULL, NULL, NULL);
}

int sqlite_rollback_transaction(sqlite3 *db) {
	return sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
}

static int sqlite_bind(sqlite3_stmt *stmt, struct json_object *arr) {
	int retval = 0;
	int len = json_object_array_length(arr);
	int i = 0;
	for (i = 0; i < len; i++) {
		json_object *obj = json_object_array_get_idx(arr, i);
		switch (json_object_get_type(obj)) {
		case json_type_null: {
			sqlite3_bind_null(stmt, i + 1);
		}
			break;

		case json_type_string: {
			const char *str = json_object_get_string(obj);
			sqlite3_bind_text(stmt, i + 1, str, strlen(str), SQLITE_TRANSIENT);
		}
			break;

		case json_type_int: {
			int n = json_object_get_int(obj);
			sqlite3_bind_int(stmt, i + 1, n);
		}
			break;

		case json_type_double: {
			double n = json_object_get_double(obj);
			sqlite3_bind_double(stmt, i + 1, n);
		}
			break;

		case json_type_object: {
			const char *file = json_object_get_string(json_object_object_get(obj, "file"));
			void *blob = NULL;
			size_t size = 0;
			if (g_str_has_prefix(file, "http://")) {
				SoupSession *session = soup_session_sync_new();
				SoupMessage *msg = soup_message_new(SOUP_METHOD_GET, file);
				soup_session_send_message(session, msg);

				blob = g_malloc(msg->response_body->length + 1);
				memcpy(blob, msg->response_body->data, size);

				g_object_unref(msg);
				soup_session_abort(session);
				g_object_unref(session);
			} else {
				g_file_get_contents(file, (gchar **) &blob, &size, NULL);
			}
			if (blob) {
				sqlite3_bind_blob(stmt, i + 1, blob, size, SQLITE_TRANSIENT);
				g_free(blob);
			}
		}
			break;

		case json_type_array: {
			sqlite_bind(stmt, obj);
		}
			break;

		default:
			break;
		}
	}

	retval = sqlite3_step(stmt);
	if (retval != SQLITE_DONE) {
		return retval;
	}
	sqlite3_reset(stmt);

	return retval;
}

json_object *sqlite_exec(json_object *params) {
	const char *filename = json_object_get_string(json_object_object_get(params, "file"));
	const char *sql = json_object_get_string(json_object_object_get(params, "sql"));
	json_object *_params = json_object_object_get(params, "params");

	sqlite3 *db = NULL;
	char *errmsg = NULL;
	int retval = 0;
	sqlite3_stmt *stmt = NULL;
	json_object *result = json_object_new_array();

	db = sqlite_open(filename);
	if (!db) {
		return result;
	}

	retval = sqlite3_prepare(db, sql, -1, &stmt, (const char **) &errmsg);
	if (retval != SQLITE_OK) {
		printf("%s\n", errmsg);
		sqlite3_close(db);
		return result;
	}

	sqlite_begin_transaction(db);

	if (_params && json_object_is_type(_params, json_type_array)) {
		retval = sqlite_bind(stmt, _params);
	} else {
		retval = sqlite3_step(stmt);
	}

	sqlite_commit_transaction(db);

	while (SQLITE_ROW == retval) {
		int colcount = sqlite3_column_count(stmt);
		struct json_object *obj = json_object_new_object();
		for (int i = 0; i < colcount; i++) {
			int type = sqlite3_column_type(stmt, i);
			const char *column_name = sqlite3_column_name(stmt, i);
			switch (type) {
			case SQLITE_INTEGER:
				json_object_object_add(obj, column_name, json_object_new_int(sqlite3_column_int(stmt, i)));
				break;

			case SQLITE_FLOAT:
				json_object_object_add(obj, column_name, json_object_new_double(sqlite3_column_double(stmt, i)));
				break;

			case SQLITE_TEXT:
				json_object_object_add(obj, column_name, json_object_new_string((const char *) sqlite3_column_text(stmt, i)));
				break;

			case SQLITE_BLOB: {
				const void *blob = sqlite3_column_blob(stmt, i);
				int size = sqlite3_column_bytes(stmt, i);
				gchar *str = g_base64_encode((guchar *) blob, size);
				json_object_object_add(obj, column_name, json_object_new_string((const char *) str));
				g_free(str);
			}
				break;

			case SQLITE_NULL:
				json_object_object_add(obj, column_name, NULL);
				break;

			default:
				json_object_object_add(obj, column_name, json_object_new_string((const char *) sqlite3_column_text(stmt, i)));
				break;
			}
		}
		json_object_array_add(result, obj);
		retval = sqlite3_step(stmt);
	}

	sqlite3_finalize(stmt);
	sqlite3_close(db);

	return result;
}
